package dataset;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;


/**
 * This class logs interesting statistical data about the
 * use of our program. Also gives the user a chance to undo
 * state when he/she makes silly changes.
 * 
 * @author Paul Dannenberg
 * 
 * @param <K>
 * @param <V>
 */
public class DataLogger<K extends Comparable<K>, V extends Comparable<V>> {

    private static final int MAX_COMMANDS_TO_SAVE = 10;
    private Map<DataSet<K, V>, Stack<IResettableCommand<K, V>>> myLoggedCommands;
    private Map<ICommand<K, V>, Integer> myUsedCommands;

    public DataLogger () {
        myLoggedCommands = new HashMap<DataSet<K, V>, Stack<IResettableCommand<K, V>>>();
        myUsedCommands = new HashMap<ICommand<K, V>, Integer>();
    }

    /**
     * Logs an action, allowing it to be undone later.
     * 
     * @param command The action to be logged.
     * @param logger The object the command was executed on.
     */
    public void log (IResettableCommand<K, V> command, DataSet<K, V> logger) {
        record(command);
        if (myLoggedCommands.containsKey(logger)) {
            myLoggedCommands.get(logger).push(command);
        }
        else {
            Stack<IResettableCommand<K, V>> commands = new Stack<IResettableCommand<K, V>>();
            commands.push(command);
            myLoggedCommands.put(logger, commands);
        }
        checkSize(logger);
    }

    /**
     * Records the use of a command for statistical purposes.
     * Commands that are passed through to this method are
     * not logged so cannot be undone by this class. Commands
     * that the user wishes to be able to undo should be passed
     * to the method log(IResettableCommand, DataSet).
     * 
     * @param command The command to be recorded.
     */
    public void record (ICommand<K, V> command) {
        if (myUsedCommands.containsKey(command)) {
            myUsedCommands.put(command, myUsedCommands.get(command) + 1);
        }
        else {
            myUsedCommands.put(command, 1);
        }
    }

    private void checkSize (DataSet<K, V> logger) {
        if (myLoggedCommands.get(logger).size() > MAX_COMMANDS_TO_SAVE) {
            myLoggedCommands.get(logger).pop();
        }
    }

    /**
     * Undoes the last action that was performed and logged.
     * 
     * @param command The command to reset.
     * @param toReset The Object the command originally executed on.
     */
    public void undo (IResettableCommand<K, V> command, DataSet<K, V> toReset) {
        if (myLoggedCommands.containsKey(toReset) &&
                !myLoggedCommands.get(toReset).empty()) {
            IResettableCommand<K, V> commandToReset = myLoggedCommands.get(
                    toReset).pop();
            commandToReset.reset(toReset);
        }
    }

    /**
     * 
     * @return The user's most frequently used command from
     *         commands recorded by the method record(ICommand).
     */
    public String getFavoriteCommand () {
        int max = 0;
        String favoriteCommand = null;
        Set<ICommand<T, S>> commands = myUsedCommands.keySet();
        for (ICommand<T, S> usedCommand : commands) {
            if (myUsedCommands.get(usedCommand) > max) {
                max = myUsedCommands.get(usedCommand);
                favoriteCommand = usedCommand.getName();
            }
        }
        return favoriteCommand;
    }

    /**
     * The total number of commands the user has issued
     * while the program has been running and that were
     * recorded by the method record(ICommand).
     * 
     * @return
     */
    public int totalNumberCommandsUsed () {
        Collection<Integer> numberEachCommandUsed = myUsedCommands.values();
        int totalCount = 0;
        for (int num : numberEachCommandUsed) {
            totalCount += num;
        }
        return totalCount;
    }

    /**
     * Returns the percent that a particular command was
     * used out of all commands that were recorded by the
     * method record(ICommand).
     * 
     * @param command Used for finding out often this command used
     * @return
     */
    public double percentCommandUsed (ICommand<K, V> command) {
        return myUsedCommands.get(command) / totalNumberCommandsUsed();
    }
}
